import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

public class CPU
implements RunneeWithInput {

    public static final String NORMAL_MESSAGE = "Program halted normally\n";

    public static final String ABNORMAL_MESSAGE =
    "Program halted abnormally:\nIllegal instruction\n";

    public static final String LDA_DESC =
    "loads the contents of memory location xx into the accumulator";

    public static final String STO_DESC =
    "stores the accumulator value into memory location xx";

    public static final String ADD_DESC =
    "adds the contents of memory location xx to the accumulator";

    public static final String SUB_DESC =
    "subtracts the contents of memory location xx from the accumulator";

    public static final String IN_DESC =
    "moves input into the accumulator";

    public static final String OUT_DESC =
    "moves the accumulator value to output";

    public static final String HLT_DESC =
    "halts the computer";

    public static final String BRZ_DESC =
    "branches to memory location xx if the accumulator value is 0";

    public static final String BRP_DESC =
    "branches to memory location xx if the accumulator value is positive or 0";

    public static final String BR_DESC =
    "branches unconditionally to memory location xx";

    public static final String DAT_DESC =
    "loads the contents of memory location xx into the accumulator";

    private Map machineInstructions = new HashMap();

    private int accumulator;
    private int programCounter;

    private JTextField accumulatorField;
    private JTextField programCounterField;
    private JTextArea statusArea;

    private Runner.Controller controller;

    private Instruction getInstruction(int insCode) {
	int opcode = (insCode/100)*100;
	if (opcode == 900) {
	    opcode = insCode;
	}
	return (Instruction)machineInstructions.get(new Integer(opcode));
    }

    private int getOperand(int insCode) {
	return insCode%100;
    }

    private void addInstruction(Instruction ins) {
	machineInstructions.put(new Integer(ins.getOpcode()), ins);
    }

    private void terminate(String msg) {
	controller.stop();
	setStatus(msg);
    }

    public CPU() {
	super();
	machineInstructions = new HashMap();
	// Add instructions, excluding aliases and directives.
	// Constructor parameters are mnemonic, description, opcode, and
	// operand size.
	// The opcode should be a full 3-digit number with 0 for the last
	// two digits except for the IN and OUT mnemonics.
	// The opcode size should be 0 for no operand, 2 for a memory
	// address and 3 for a number (DAT mnemonics).
	addInstruction(new Instruction("LDA", LDA_DESC, 500, 2) {
	    public void execute(int opnd) {
		int unsignedValue = LMC.getMemory().get(opnd);
		if (unsignedValue >= 500) {
		    accumulator = unsignedValue - 1000;
		} else {
		    accumulator = unsignedValue;
		}
	    }
	});
	addInstruction(new Instruction("STO", STO_DESC, 300, 2) {
	    public void execute(int opnd) {
		if (accumulator < 0) {
		    LMC.getMemory().set(opnd, accumulator + 1000);
		} else {
		    LMC.getMemory().set(opnd, accumulator);
		}
	    }
	});
	addInstruction(new Instruction("ADD", ADD_DESC, 100, 2) {
	    public void execute(int opnd) {
		accumulator += LMC.getMemory().get(opnd);
		if (accumulator < -500) {
		    accumulator += 1000;
		}
		if (accumulator >= 500) {
		    accumulator -= 1000;
		}
	    }
	});
	addInstruction(new Instruction("SUB", SUB_DESC, 200, 2) {
	    public void execute(int opnd) {
		accumulator -= LMC.getMemory().get(opnd);
		if (accumulator < -500) {
		    accumulator += 1000;
		}
		if (accumulator >= 500) {
		    accumulator -= 1000;
		}
	    }
	});
	addInstruction(new Instruction("IN", IN_DESC, 901, 0) {
	    public void execute(int opnd) {
		controller.hold();
		LMC.getIO().initiateInput();
	    }
	});
	addInstruction(new Instruction("OUT", OUT_DESC, 902, 0) {
	    public void execute(int opnd) {
		LMC.getIO().output(accumulator);
	    }
	});
	addInstruction(new Instruction("HLT", HLT_DESC, 0, 0) {
	    public void execute(int opnd) {
		terminate(NORMAL_MESSAGE);
	    }
	});
	addInstruction(new Instruction("BRZ", BRZ_DESC, 700, 2) {
	    public void execute(int opnd) {
		if (accumulator == 0) {
		    programCounter = opnd;
		}
	    }
	});
	addInstruction(new Instruction("BRP", BRP_DESC, 800, 2) {
	    public void execute(int opnd) {
		if (accumulator >= 0) {
		    programCounter = opnd;
		}
	    }
	});
	addInstruction(new Instruction("BR", BR_DESC, 600, 2) {
	    public void execute(int opnd) {
		programCounter = opnd;
	    }
	});
	// Add aliases and directives.
	// Do not add to machineInstructions.
	new Instruction("COB", HLT_DESC, 0, 0) {
	    public void execute(int opnd) {
		terminate(NORMAL_MESSAGE);
	    }
	};
	new Instruction("DAT", DAT_DESC, 0, 3) {
	    public void execute(int opnd) {
	    }
	};
    }

    public void setRunnerController(Runner.Controller rc) {
	controller = rc;
    }

    public JComponent createVisual() {
	// FIXME - use helper method for wrapping components.
	accumulatorField = new JTextField(10);
	accumulatorField.setEditable(false);
        accumulatorField.setFont(LMC.font);
	JPanel accumulatorPanel = new JPanel(new FlowLayout());
	accumulatorPanel.setBorder(
		BorderFactory.createTitledBorder("Accumulator"));
	accumulatorPanel.add(accumulatorField);
	programCounterField = new JTextField(10);
	programCounterField.setEditable(false);
        programCounterField.setFont(LMC.font);
	JPanel programCounterPanel = new JPanel(new FlowLayout());
	programCounterPanel.setBorder(
		BorderFactory.createTitledBorder("Location"));
	programCounterPanel.add(programCounterField);
	JPanel statePanel = new JPanel(new GridLayout(1, 2));
	statePanel.add(accumulatorPanel);
	statePanel.add(programCounterPanel);
	statusArea = new JTextArea(15, 30);
	statusArea.setEditable(false);
        statusArea.setFont(LMC.font);
	JScrollPane statusScrollPane = new JScrollPane(statusArea,
		JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
		JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	statusScrollPane.setBorder(BorderFactory.createTitledBorder("Status"));
	JPanel cpuPanel = new JPanel(new BorderLayout());
	cpuPanel.add(statePanel, BorderLayout.NORTH);
	cpuPanel.add(statusScrollPane, BorderLayout.CENTER);
	return cpuPanel;
    }

    public void updateDisplay() {
	accumulatorField.setText("" + accumulator);
	programCounterField.setText("" + programCounter);
    }

    public void setStatus(String str) {
	statusArea.setText(str);
    }

    public void appendStatus(String str) {
	statusArea.append(str);
    }

    public void doStep() {
	int insCode = LMC.getMemory().get(programCounter);
	programCounter = (programCounter + 1)%100;
	Instruction instruction = getInstruction(insCode);
	if (instruction == null) {
	    terminate(ABNORMAL_MESSAGE);
	} else {
	    int operand = getOperand(insCode);
	    instruction.execute(operand);
	}
    }

    public void initialize() {
	accumulator = 0;
	programCounter = 0;
	statusArea.setText("");
	controller.resume();
    }

    public void putInput(int val) {
	accumulator = val;
	statusArea.setText("");
	controller.resume();
    }

}

